source('../env.R')

Species in communities

It seems reasonable to expect that cities with simialr regional pools will have similar species entering the city, and thus a similar response to urbanisation.

Load data

city_effort = read_csv(filename(CITY_DATA_OUTPUT_DIR, 'city_effort.csv'))
Rows: 342 Columns: 7── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): city_name
dbl (6): city_id, total_city_checklists, total_city_locations, total_city_effort, total_city_area_m2, percentage_total_city_area_surveyed
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
city_effort
communities = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'communities_for_analysis.csv'))
Rows: 2462 Columns: 10── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (5): city_name, jetz_species_name, seasonal, presence, origin
dbl (2): city_id, relative_abundance_proxy
lgl (3): present_urban_high, present_urban_med, present_urban_low
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
communities_summary = communities %>% group_by(city_id) %>% summarise(
  regional_pool_size = n(), 
  urban_pool_size = sum(relative_abundance_proxy > 0)
) %>% left_join(city_effort %>% dplyr::select(city_id, percentage_total_city_area_surveyed))
Joining with `by = join_by(city_id)`
ggplot(communities %>% filter(relative_abundance_proxy > 0), aes(x = relative_abundance_proxy)) + geom_bar(stat = "bin")

city_points = st_centroid(read_sf(filename(CITY_DATA_OUTPUT_DIR, 'city_selection.shp')))
Warning: st_centroid assumes attributes are constant over geometriesWarning: st_centroid does not give correct centroids for longitude/latitude data
community_data_metrics = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'community_assembly_metrics_using_relative_abundance.csv')) %>%
  dplyr::select(city_id, mntd_normalised, fdiv_normalised, mass_fdiv_normalised, locomotory_trait_fdiv_normalised, trophic_trait_fdiv_normalised, gape_width_fdiv_normalised) %>%
  left_join(read_csv(filename(CITY_DATA_OUTPUT_DIR, 'realms.csv'))) %>%
  left_join(communities_summary) %>%
  left_join(city_points[,c('city_id', 'city_nm')]) %>%
  rename(city_name='city_nm') %>%
  na.omit() %>%
  arrange(city_id)
Rows: 341 Columns: 37── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (37): mntd_normalised, mntd_actual, mntd_min, mntd_max, mntd_mean, mntd_sd, fdiv_normalised, fdiv_actual, fdiv_min, fdiv_max, fdiv_mean, fdiv_sd, mass_fdi...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 342 Columns: 2── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): core_realm
dbl (1): city_id
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(city_id)`Joining with `by = join_by(city_id)`Joining with `by = join_by(city_id)`
community_data_metrics

Load trait data

traits = read_csv(filename(TAXONOMY_OUTPUT_DIR, 'traits_jetz.csv'))
Rows: 304 Columns: 5── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): jetz_species_name
dbl (4): gape_width, trophic_trait, locomotory_trait, mass
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(traits)
fetch_normalised_traits = function(required_species_list) {
  required_traits = traits %>% filter(jetz_species_name %in% required_species_list)
  
  required_traits$gape_width_normalised = normalise(required_traits$gape_width, min(required_traits$gape_width), max(required_traits$gape_width))
  required_traits$trophic_trait_normalised = normalise(required_traits$trophic_trait, min(required_traits$trophic_trait), max(required_traits$trophic_trait))
  required_traits$locomotory_trait_normalised = normalise(required_traits$locomotory_trait, min(required_traits$locomotory_trait), max(required_traits$locomotory_trait))
  required_traits$mass_normalised = normalise(required_traits$mass, min(required_traits$mass), max(required_traits$mass))
  
  traits_normalised_long = required_traits %>% pivot_longer(cols = c('gape_width_normalised', 'trophic_trait_normalised', 'locomotory_trait_normalised', 'mass_normalised'), names_to = 'trait', values_to = 'normalised_value') %>% dplyr::select(jetz_species_name, trait, normalised_value)
  traits_normalised_long$trait = factor(traits_normalised_long$trait, levels = c('gape_width_normalised', 'trophic_trait_normalised', 'locomotory_trait_normalised', 'mass_normalised'), labels = c('Gape Width', 'Trophic Trait', 'Locomotory Trait', 'Mass'))
  
  traits_normalised_long
}

fetch_normalised_traits(c('Aplopelia_larvata', 'Chalcophaps_indica', 'Caloenas_nicobarica'))

Read in our phylogeny

phylo_tree = read.tree(filename(TAXONOMY_OUTPUT_DIR, 'phylogeny.tre'))
ggtree(phylo_tree, layout='circular')

Load resolve ecoregions

resolve = read_resolve()
Warning: st_buffer does not correctly buffer longitude/latitude datadist is assumed to be in decimal degrees (arc_degrees).
Warning: st_simplify does not correctly simplify longitude/latitude data, dTolerance needs to be in decimal degrees

Create helper functions

to_species_matrix = function(filtered_communities) {
  filtered_communities %>% 
    dplyr::select(city_id, jetz_species_name) %>% 
    distinct() %>%
    mutate(present = TRUE) %>% 
    pivot_wider(
      names_from = jetz_species_name, 
      values_from = "present", 
      values_fill = list(present = F)
    ) %>% 
    tibble::column_to_rownames(var='city_id')
}
community_nmds = function(filtered_communities) {
  species_matrix = to_species_matrix(filtered_communities)
  nmds <- metaMDS(species_matrix, k=2, trymax = 30)
  nmds_result = data.frame(vegan::scores(nmds)$sites)
  nmds_result$city_id = as.double(rownames(nmds_result))
  rownames(nmds_result) = NULL
  nmds_result
}

https://www.datacamp.com/tutorial/k-means-clustering-r

scree_plot = function(community_nmds_data) {
  # Decide how many clusters to look at
  n_clusters <- min(10, nrow(community_nmds_data) - 1)
  
  # Initialize total within sum of squares error: wss
  wss <- numeric(n_clusters)
  
  set.seed(123)
  
  # Look over 1 to n possible clusters
  for (i in 1:n_clusters) {
    # Fit the model: km.out
    km.out <- kmeans(community_nmds_data[,c('NMDS1','NMDS2')], centers = i, nstart = 20)
    # Save the within cluster sum of squares
    wss[i] <- km.out$tot.withinss
  }
  
  # Produce a scree plot
  wss_df <- tibble(clusters = 1:n_clusters, wss = wss)
   
  scree_plot <- ggplot(wss_df, aes(x = clusters, y = wss, group = 1)) +
      geom_point(size = 4) +
      geom_line() +
      geom_hline(linetype="dashed", color = "orange", yintercept = wss) +
      scale_x_continuous(breaks = c(2, 4, 6, 8, 10)) +
      xlab('Number of clusters')
  scree_plot
}
cluster_cities = function(city_nmds, cities_community_data, centers) {
  set.seed(123)
  kmeans_clusters <- kmeans(city_nmds[,c('NMDS1', 'NMDS2')], centers = centers, nstart = 20)
  city_nmds$cluster = kmeans_clusters$cluster
  cities_community_data %>% left_join(city_nmds) %>% mutate(cluster = as.factor(cluster))
}
plot_nmds_clusters = function(cluster_cities) {
  cluster_cities %>% dplyr::select(city_id, city_name, NMDS1, NMDS2, cluster) %>% distinct() %>%
  ggplot(aes(x = NMDS1, y = NMDS2, colour = cluster)) + geom_point() + geom_label_repel(aes(label = city_name))
}
get_presence_cell_width = function(city_cluster_data_metrics) {
  10 * length(unique(city_cluster_data_metrics$city_id))
}

get_presence_cell_height = function(city_cluster_data_metrics) {
  species = species_in_cluster = communities %>% 
    filter(city_id %in% city_cluster_data_metrics$city_id) %>% 
    dplyr::select(jetz_species_name) %>% 
    distinct()
  
  10 * nrow(species)
}

city_metric_height = 30
traits_width = 50
phylo_tree_width = 125
title_height = 8

get_image_height = function(city_cluster_data_metrics) {
  get_presence_cell_height(city_cluster_data_metrics) + (2 * city_metric_height) + title_height
}

get_image_width = function(city_cluster_data_metrics) {
  get_presence_cell_width(city_cluster_data_metrics) + traits_width + phylo_tree_width
}
test_value = function(name, normalised_list) {
  wilcox_test_result = wilcox.test(normalised_list, mu = 0.5)
  
  significance = ifelse(wilcox_test_result$p.value < 0.0001, '***', 
                        ifelse(wilcox_test_result$p.value < 0.001, '**', 
                               ifelse(wilcox_test_result$p.value < 0.01, '*', '')))
  m = mean(normalised_list)
  
  paste(name, 'mean', round(m, 2), significance)
}
plot_city_cluster = function(city_cluster_data_metrics, title) {
  species_in_cluster = communities %>% 
    filter(city_id %in% city_cluster_data_metrics$city_id) %>% 
    dplyr::select(jetz_species_name, city_name, relative_abundance_proxy)
  
  tree_cropped <- ladderize(drop.tip(phylo_tree, setdiff(phylo_tree$tip.label, species_in_cluster$jetz_species_name)))
    
  gg_tree = ggtree(tree_cropped)
  
  gg_presence = ggplot(species_in_cluster, aes(x=city_name, y=jetz_species_name)) + 
          geom_tile(aes(fill=relative_abundance_proxy)) + 
          scale_fill_gradientn(colours=c("#98FB98", "#FFFFE0", "yellow", "orange", "#FF4500", "red", "red"), values=c(0, 0.00000000001, 0.1, 0.25, 0.5, 0.75, 1), na.value = "transparent") +
          theme_minimal() + xlab(NULL) + ylab(NULL) + 
          theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) + 
          labs(fill='Urban Proxy Abundance')
  
  species_in_cluster_traits = fetch_normalised_traits(species_in_cluster$jetz_species_name)
  
  gg_traits = ggplot(species_in_cluster_traits, aes(x = trait, y = jetz_species_name, size = normalised_value)) + geom_point() + theme_minimal() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1), axis.text.y=element_blank()) + xlab(NULL) + ylab(NULL) + labs(size = "Normalised Value")
  
  gg_cities_mntd = ggplot(city_cluster_data_metrics, aes(x = city_name, y = mntd_normalised)) + geom_bar(stat = "identity") + theme_minimal() + theme(legend.position = "none", axis.text.x=element_blank()) + xlab(NULL) + ylab("MNTD") + ylim(0, 1)
  
  gg_cities_fd = ggplot(city_cluster_data_metrics, aes(x = city_name, y = fdiv_normalised)) + geom_bar(stat = "identity") + theme_minimal() + theme(legend.position = "none", axis.text.x=element_blank()) + xlab(NULL) + ylab("FDiv") + ylim(0, 1)
  
  gg_title = ggplot() + labs(title = title, subtitle = paste(
    test_value('MNTD', city_cluster_data_metrics$mntd_normalised),
    test_value('FDiv', city_cluster_data_metrics$fdiv_normalised),
    test_value('Locomotory trait', city_cluster_data_metrics$locomotory_trait_fdiv_normalised),
    test_value('Gape width', city_cluster_data_metrics$gape_width_fdiv_normalised),
    sep = '\n'
  )) + theme_minimal() + theme(plot.subtitle=element_text(size=8, hjust=0, color="#444444"))
  
  gg_presence_height = get_presence_cell_height(city_cluster_data_metrics)
  gg_presence_width = get_presence_cell_width(city_cluster_data_metrics)
  
  gg_presence %>% insert_top(gg_cities_mntd, height = (city_metric_height / gg_presence_height)) %>% insert_top(gg_cities_fd, height = (city_metric_height / gg_presence_height)) %>% insert_left(gg_tree, width = (phylo_tree_width / gg_presence_width)) %>% insert_right(gg_traits, width = (traits_width / gg_presence_width)) %>% insert_top(gg_title, height = (title_height / gg_presence_height))
}
REGION_DEEP_DIVE_FIGURES_OUTPUT = mkdir(FIGURES_OUTPUT_DIR, 'appendix_regional_deep_dive_using_abundance')

Nearctic

nearctic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Nearctic')
nearctic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "San Jose"                 "Los Angeles"              "Concord"                  "Tijuana"                  "Bakersfield"             
 [6] "Fresno"                   "Sacramento"               "Mexicali"                 "Hermosillo"               "Las Vegas"               
[11] "Phoenix"                  "Tucson"                   "Durango"                  "Portland"                 "Chihuahua"               
[16] "Aguascalientes"           "Seattle"                  "Ciudad Juárez"            "San Luis Potosí"          "Mexico City"             
[21] "Saltillo"                 "Vancouver"                "Salt Lake City"           "Albuquerque"              "Monterrey"               
[26] "Nuevo Laredo"             "San Antonio"              "Denver"                   "Austin"                   "Houston"                 
[31] "Dallas"                   "Oklahoma City"            "Calgary"                  "New Orleans"              "Kansas City"             
[36] "Omaha"                    "St. Louis"                "Bradenton"                "Tampa"                    "Minneapolis [Saint Paul]"
[41] "Atlanta"                  "Orlando"                  "Louisville"               "Chicago"                  "Indianapolis"            
[46] "Milwaukee"               

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
nearctic_cities_nmds = community_nmds(communities %>% filter(city_id %in% nearctic_cities_community_data$city_id)) 
Run 0 stress 0.1005678 
Run 1 stress 0.1217145 
Run 2 stress 0.1005678 
... New best solution
... Procrustes: rmse 0.000005998266  max resid 0.00001126393 
... Similar to previous best
Run 3 stress 0.1000046 
... New best solution
... Procrustes: rmse 0.007230917  max resid 0.03469213 
Run 4 stress 0.1212382 
Run 5 stress 0.1000046 
... Procrustes: rmse 0.000005864402  max resid 0.00001784078 
... Similar to previous best
Run 6 stress 0.1022505 
Run 7 stress 0.1228731 
Run 8 stress 0.1000046 
... Procrustes: rmse 0.000007006229  max resid 0.00001820683 
... Similar to previous best
Run 9 stress 0.1234704 
Run 10 stress 0.1000046 
... Procrustes: rmse 0.000004920211  max resid 0.00001580423 
... Similar to previous best
Run 11 stress 0.1000046 
... Procrustes: rmse 0.000003597992  max resid 0.00001215378 
... Similar to previous best
Run 12 stress 0.1329412 
Run 13 stress 0.1000046 
... Procrustes: rmse 0.000007362064  max resid 0.00002393795 
... Similar to previous best
Run 14 stress 0.1012776 
Run 15 stress 0.1227326 
Run 16 stress 0.1217437 
Run 17 stress 0.1000046 
... Procrustes: rmse 0.000006861305  max resid 0.00002056524 
... Similar to previous best
Run 18 stress 0.1212382 
Run 19 stress 0.1233274 
Run 20 stress 0.1226495 
*** Best solution repeated 6 times
nearctic_cities_nmds
scree_plot(nearctic_cities_nmds)

nearctic_cities = cluster_cities(city_nmds = nearctic_cities_nmds, cities_community_data = nearctic_cities_community_data, centers = 4)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(nearctic_cities)

nearctic_biomes = st_crop(resolve[resolve$REALM == 'Nearctic',c('REALM')], xmin = -220, ymin = 0, xmax = 0, ymax = 70)
although coordinates are longitude/latitude, st_intersection assumes that they are planar
Warning: attribute variables are assumed to be spatially constant throughout all geometries
 
ggplot() + 
  geom_sf(data = nearctic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = nearctic_cities, aes(geometry = geometry, color = cluster))

ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_clusters.jpg'))
Saving 7.29 x 4.51 in image

Neartic Cluster 1`

nearactic_cluster1 = nearctic_cities %>% filter(cluster == 1)
plot_city_cluster(nearactic_cluster1, 'Neartic cluster 1')
Warning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster1.jpg'), width = get_image_width(nearactic_cluster1), height = get_image_height(nearactic_cluster1), units = "mm")

Neartic Cluster 2

nearactic_cluster2 = nearctic_cities %>% filter(cluster == 2)
plot_city_cluster(nearactic_cluster2, 'Neartic cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster2.jpg'), width = get_image_width(nearactic_cluster2), height = get_image_height(nearactic_cluster2), units = "mm")

Neartic Cluster 3

nearactic_cluster3 = nearctic_cities %>% filter(cluster == 3)
plot_city_cluster(nearactic_cluster3, 'Neartic cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster3.jpg'), width = get_image_width(nearactic_cluster3), height = get_image_height(nearactic_cluster3), units = "mm")

Neartic Cluster 4

nearactic_cluster4 = nearctic_cities %>% filter(cluster == 4)
plot_city_cluster(nearactic_cluster4, 'Neartic cluster 4')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neartic_cluster4.jpg'), width = get_image_width(nearactic_cluster4), height = get_image_height(nearactic_cluster4), units = "mm")

Neotropic

neotropic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Neotropic')
neotropic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "Culiacán"                  "Guadalajara"               "Morelia"                   "Acapulco"                  "Querétaro"                
 [6] "Cuernavaca"                "Puebla"                    "Oaxaca"                    "Xalapa"                    "Veracruz"                 
[11] "Tuxtla Gutiérrez"          "Villahermosa"              "Guatemala City"            "San Salvador"              "San Pedro Sula"           
[16] "Mérida"                    "Tegucigalpa"               "Managua"                   "San José"                  "Cancún"                   
[21] "Guayaquil"                 "Chiclayo"                  "Panama City"               "Trujillo"                  "Quito"                    
[26] "Havana"                    "Cali"                      "Lima"                      "Pereira"                   "Miami"                    
[31] "Medellín"                  "Ibagué"                    "Cartagena"                 "Kingston"                  "Bogota"                   
[36] "Barranquilla"              "Bucaramanga"               "Cúcuta"                    "Maracaibo"                 "Arequipa"                 
[41] "Barquisimeto"              "Santo Domingo"             "Maracay"                   "El Alto [La Paz]"          "Caracas"                  
[46] "Cochabamba"                "Viña del Mar [Valparaíso]" "Río Piedras [San Juan]"    "Barcelona"                 "Concepción"               
[51] "Santiago"                  "Mendoza"                   "Salta"                     "Cordoba"                   "Asuncion"                 
[56] "Buenos Aires"              "La Plata"                  "Ciudad del Este"           "Montevideo"                "Mar del Plata"            
[61] "Porto Alegre"              "São Paulo"                 "Santos"                    "Sao Jose dos Campos"      

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
neotropic_cities_nmds = community_nmds(communities %>% filter(city_id %in% neotropic_cities_community_data$city_id)) 
Run 0 stress 0.134619 
Run 1 stress 0.141222 
Run 2 stress 0.1406945 
Run 3 stress 0.1348238 
... Procrustes: rmse 0.01071269  max resid 0.05473585 
Run 4 stress 0.1346206 
... Procrustes: rmse 0.000608974  max resid 0.003438253 
... Similar to previous best
Run 5 stress 0.1405635 
Run 6 stress 0.141222 
Run 7 stress 0.1363665 
Run 8 stress 0.134433 
... New best solution
... Procrustes: rmse 0.006823274  max resid 0.04569756 
Run 9 stress 0.134433 
... New best solution
... Procrustes: rmse 0.00001542252  max resid 0.0000581339 
... Similar to previous best
Run 10 stress 0.1348047 
... Procrustes: rmse 0.006556237  max resid 0.04610514 
Run 11 stress 0.1346369 
... Procrustes: rmse 0.006940877  max resid 0.04570433 
Run 12 stress 0.1348237 
... Procrustes: rmse 0.006643915  max resid 0.04602606 
Run 13 stress 0.1346368 
... Procrustes: rmse 0.00693036  max resid 0.04572126 
Run 14 stress 0.1346407 
... Procrustes: rmse 0.007301507  max resid 0.04567957 
Run 15 stress 0.1348046 
... Procrustes: rmse 0.006549538  max resid 0.04606878 
Run 16 stress 0.1346226 
... Procrustes: rmse 0.007181977  max resid 0.04572151 
Run 17 stress 0.1346406 
... Procrustes: rmse 0.00728238  max resid 0.04571987 
Run 18 stress 0.1348046 
... Procrustes: rmse 0.006548102  max resid 0.04606033 
Run 19 stress 0.1346226 
... Procrustes: rmse 0.007198145  max resid 0.04569021 
Run 20 stress 0.1346225 
... Procrustes: rmse 0.00713155  max resid 0.04570237 
*** Best solution repeated 1 times
neotropic_cities_nmds
scree_plot(neotropic_cities_nmds)

neotropic_cities = cluster_cities(city_nmds = neotropic_cities_nmds, cities_community_data = neotropic_cities_community_data, centers = 5)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(neotropic_cities)

neotropic_biomes = resolve[resolve$REALM == 'Neotropic',c('REALM')]
 
ggplot() + 
  geom_sf(data = neotropic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = neotropic_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_clusters.jpg'))
Saving 7.29 x 4.51 in image

Neotropic Cluster 1

neotropic_cluster1 = neotropic_cities %>% filter(cluster == 1)
plot_city_cluster(neotropic_cluster1, 'Neotropic cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster1.jpg'), width = get_image_width(neotropic_cluster1), height = get_image_height(neotropic_cluster1), units = "mm")

Neotropic Cluster 2

neotropic_cluster2 = neotropic_cities %>% filter(cluster == 2)
plot_city_cluster(neotropic_cluster2, 'Neotropic cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster2.jpg'), width = get_image_width(neotropic_cluster2), height = get_image_height(neotropic_cluster2), units = "mm")

Neotropic Cluster 3

neotropic_cluster3 = neotropic_cities %>% filter(cluster == 3)
plot_city_cluster(neotropic_cluster3, 'Neotropic cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster3.jpg'), width = get_image_width(neotropic_cluster3), height = get_image_height(neotropic_cluster3), units = "mm")

Neotropic Cluster 4

neotropic_cluster4 = neotropic_cities %>% filter(cluster == 4)
plot_city_cluster(neotropic_cluster4, 'Neotropic cluster 4')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster4.jpg'), width = get_image_width(neotropic_cluster4), height = get_image_height(neotropic_cluster4), units = "mm")

Neotropic Cluster 5

neotropic_cluster5 = neotropic_cities %>% filter(cluster == 5)
plot_city_cluster(neotropic_cluster5, 'Neotropic cluster 5')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'neotropic_cluster5.jpg'), width = get_image_width(neotropic_cluster5), height = get_image_height(neotropic_cluster5), units = "mm")

Palearctic

palearctic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Palearctic')
palearctic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "Lisbon"                "Porto"                 "Marrakesh"             "Seville"               "Dublin"                "Málaga"               
 [7] "Madrid"                "Glasgow"               "Bilbao"                "Liverpool"             "Bristol"               "Manchester"           
[13] "Birmingham"            "Leeds"                 "Newcastle upon Tyne"   "Sheffield"             "Nottingham"            "Valencia"             
[19] "London"                "Toulouse"              "Paris"                 "Barcelona"             "Rotterdam [The Hague]" "Brussels"             
[25] "Amsterdam"             "Lyon"                  "Marseille"             "Dusseldorf"            "Nice"                  "Frankfurt am Main"    
[31] "Zurich"                "Oslo"                  "Stuttgart"             "Hamburg"               "Genoa"                 "Nuremberg"            
[37] "Copenhagen"            "Munich"                "Berlin"                "Dresden"               "Rome"                  "Prague"               
[43] "Stockholm"             "Poznan"                "Vienna"                "Wroclaw"               "Zagreb"                "Gdansk"               
[49] "Budapest"              "Krakow"                "Warsaw"                "Helsinki"              "Riga"                  "Belgrade"             
[55] "Lviv"                  "Sofia"                 "Thessaloniki"          "Saint Petersburg"      "Minsk"                 "Athens"               
[61] "Kyiv"                  "Istanbul"              "Odesa"                 "Samsun"                "Luxor"                 "Tel Aviv"             
[67] "Jerusalem"             "Tbilisi"               "Yerevan"               "Kuwait City"           "Doha"                  "Abu Dhabi"            
[73] "Dubai"                 "Bishkek"              

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
palearctic_cities_nmds = community_nmds(communities %>% filter(city_id %in% palearctic_cities_community_data$city_id)) 
Run 0 stress 0.04961857 
Run 1 stress 0.07367425 
Run 2 stress 0.05453968 
Run 3 stress 0.0712512 
Run 4 stress 0.05826357 
Run 5 stress 0.06338732 
Run 6 stress 0.06304725 
Run 7 stress 0.08618201 
Run 8 stress 0.07925636 
Run 9 stress 0.05647129 
Run 10 stress 0.09129792 
Run 11 stress 0.0497659 
... Procrustes: rmse 0.005703935  max resid 0.01420466 
Run 12 stress 0.05000939 
... Procrustes: rmse 0.06743721  max resid 0.2226243 
Run 13 stress 0.07603764 
Run 14 stress 0.0500095 
... Procrustes: rmse 0.06741103  max resid 0.2225553 
Run 15 stress 0.096573 
Run 16 stress 0.0556821 
Run 17 stress 0.05115583 
Run 18 stress 0.08625966 
Run 19 stress 0.06766821 
Run 20 stress 0.06829368 
Run 21 stress 0.07723847 
Run 22 stress 0.05236354 
Run 23 stress 0.05642252 
Run 24 stress 0.05235961 
Run 25 stress 0.06829346 
Run 26 stress 0.05353311 
Run 27 stress 0.06421851 
Run 28 stress 0.05706493 
Run 29 stress 0.0512458 
Run 30 stress 0.05479168 
*** Best solution was not repeated -- monoMDS stopping criteria:
    14: no. of iterations >= maxit
    14: stress ratio > sratmax
     2: scale factor of the gradient < sfgrmin
palearctic_cities_nmds
scree_plot(palearctic_cities_nmds)
Warning: Quick-TRANSfer stage steps exceeded maximum (= 3700)

palearctic_cities = cluster_cities(city_nmds = palearctic_cities_nmds, cities_community_data = palearctic_cities_community_data, centers = 7)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(palearctic_cities)

palearctic_biomes = st_crop(resolve[resolve$REALM == 'Palearctic',c('REALM')], xmin = -30, ymin = 20, xmax = 80, ymax = 65)
although coordinates are longitude/latitude, st_intersection assumes that they are planar
Warning: attribute variables are assumed to be spatially constant throughout all geometries
 
ggplot() + 
  geom_sf(data = palearctic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = palearctic_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_clusters.jpg'))
Saving 7.29 x 4.51 in image

Palearctic Cluster 1

palearctic_cluster1 = palearctic_cities %>% filter(cluster == 1)
plot_city_cluster(palearctic_cluster1, 'Palearctic cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster1.jpg'), width = get_image_width(palearctic_cluster1), height = get_image_height(palearctic_cluster1), units = "mm")

Palearctic Cluster 2

palearctic_cluster2 = palearctic_cities %>% filter(cluster == 2)
plot_city_cluster(palearctic_cluster2, 'Palearctic cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster2.jpg'), width = get_image_width(palearctic_cluster2), height = get_image_height(palearctic_cluster2), units = "mm")

Palearctic Cluster 3

palearctic_cluster3 = palearctic_cities %>% filter(cluster == 3)
plot_city_cluster(palearctic_cluster3, 'Palearctic cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster3.jpg'), width = get_image_width(palearctic_cluster3), height = get_image_height(palearctic_cluster3), units = "mm")

Palearctic Cluster 4

palearctic_cluster4 = palearctic_cities %>% filter(cluster == 4)
plot_city_cluster(palearctic_cluster4, 'Palearctic cluster 4')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster4.jpg'), width = get_image_width(palearctic_cluster4), height = get_image_height(palearctic_cluster4), units = "mm")

Palearctic Cluster 5

palearctic_cluster5 = palearctic_cities %>% filter(cluster == 5)
plot_city_cluster(palearctic_cluster5, 'Palearctic cluster 5')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster5.jpg'), width = get_image_width(palearctic_cluster5), height = get_image_height(palearctic_cluster5), units = "mm")

Palearctic Cluster 6

palearctic_cluster6 = palearctic_cities %>% filter(cluster == 6)
plot_city_cluster(palearctic_cluster6, 'Palearctic cluster 6')
Warning: cannot compute exact p-value with tiesWarning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster6.jpg'), width = get_image_width(palearctic_cluster6), height = get_image_height(palearctic_cluster6), units = "mm")

Palearctic Cluster 7

palearctic_cluster7 = palearctic_cities %>% filter(cluster == 7)
plot_city_cluster(palearctic_cluster7, 'Palearctic cluster 7')
Warning: cannot compute exact p-value with ties
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'palearctic_cluster7.jpg'), width = get_image_width(palearctic_cluster7), height = get_image_height(palearctic_cluster7), units = "mm")

Afrotropic

afrotropic_cities_community_data = community_data_metrics %>% filter(core_realm == 'Afrotropic')
afrotropic_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
[1] "Cape Town"    "Johannesburg" "Pretoria"     "Kigali"       "Kampala"      "Arusha"       "Nairobi"      "Addis Ababa"  "Antananarivo"

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
afrotropic_cities_nmds = community_nmds(communities %>% filter(city_id %in% afrotropic_cities_community_data$city_id)) 
Run 0 stress 0.00009014786 
Run 1 stress 0.00009687776 
... Procrustes: rmse 0.0001795738  max resid 0.0003677254 
... Similar to previous best
Run 2 stress 0.0004880168 
... Procrustes: rmse 0.002714405  max resid 0.004216075 
... Similar to previous best
Run 3 stress 0.002197429 
Run 4 stress 0.002217343 
Run 5 stress 0.001458994 
Run 6 stress 0.001644339 
Run 7 stress 0.00009920033 
... Procrustes: rmse 0.0004847723  max resid 0.001031861 
... Similar to previous best
Run 8 stress 0.002875654 
Run 9 stress 0.0000981389 
... Procrustes: rmse 0.0002030147  max resid 0.0003530948 
... Similar to previous best
Run 10 stress 0.002867494 
Run 11 stress 0.00008849784 
... New best solution
... Procrustes: rmse 0.0002335844  max resid 0.0003221289 
... Similar to previous best
Run 12 stress 0.00009627594 
... Procrustes: rmse 0.0003174967  max resid 0.0004782772 
... Similar to previous best
Run 13 stress 0.0009759083 
Run 14 stress 0.00009560675 
... Procrustes: rmse 0.0003164071  max resid 0.0004773074 
... Similar to previous best
Run 15 stress 0.00009530359 
... Procrustes: rmse 0.0002237035  max resid 0.0002980527 
... Similar to previous best
Run 16 stress 0.0005451705 
... Procrustes: rmse 0.003139194  max resid 0.00438152 
... Similar to previous best
Run 17 stress 0.00009416695 
... Procrustes: rmse 0.000220691  max resid 0.0002981013 
... Similar to previous best
Run 18 stress 0.00009567935 
... Procrustes: rmse 0.0003049031  max resid 0.0004680001 
... Similar to previous best
Run 19 stress 0.00009355461 
... Procrustes: rmse 0.0003102717  max resid 0.0004660568 
... Similar to previous best
Run 20 stress 0.00009568572 
... Procrustes: rmse 0.0002099296  max resid 0.0002903802 
... Similar to previous best
*** Best solution repeated 9 times
Warning: stress is (nearly) zero: you may have insufficient data
afrotropic_cities_nmds
scree_plot(afrotropic_cities_nmds)

afrotropic_cities = cluster_cities(city_nmds = afrotropic_cities_nmds, cities_community_data = afrotropic_cities_community_data, centers = 2)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(afrotropic_cities)

afrotropic_biomes = resolve[resolve$REALM == 'Afrotropic',c('REALM')]
 
ggplot() + 
  geom_sf(data = afrotropic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = afrotropic_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'afrotropic_clusters.jpg'))
Saving 7.29 x 4.51 in image

Afrotropic Cluster 1

afrotropic_cluster1 = afrotropic_cities %>% filter(cluster == 1)
plot_city_cluster(afrotropic_cluster1, 'Afrotropic cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'afrotropic_cluster1.jpg'), width = get_image_width(afrotropic_cluster1), height = get_image_height(afrotropic_cluster1), units = "mm")

Afrotropic Cluster 2

afrotropic_cluster2 = afrotropic_cities %>% filter(cluster == 2)
plot_city_cluster(afrotropic_cluster2, 'Afrotropic cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'afrotropic_cluster2.jpg'), width = get_image_width(afrotropic_cluster2), height = get_image_height(afrotropic_cluster2), units = "mm")

Indomalayan

indomalayan_cities_community_data = community_data_metrics %>% filter(core_realm == 'Indomalayan')
indomalayan_cities_community_data %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
  [1] "Srinagar"            "Jamnagar"            "Jammu"               "Rajkot"              "Bikaner"             "Jodhpur"             "Jalandhar"          
  [8] "Ahmedabad"           "Bhavnagar"           "Ludhiana"            "Anand"               "Udaipur"             "Surat"               "Vadodara"           
 [15] "Ajmer"               "Chandigarh"          "Vasai-Virar"         "Mumbai"              "Jaipur"              "Delhi [New Delhi]"   "Nashik"             
 [22] "Dehradun"            "Kota"                "Pune"                "Haridwar"            "Dhule"               "Ujjain"              "Indore"             
 [29] "Ahmadnagar"          "Kolhapur"            "Jalgaon"             "Agra"                "Aurangabad"          "Sangli"              "Belagavi"           
 [36] "Gwalior"             "Budaun"              "Bareilly"            "Dharwad"             "Bhopal"              "Bhind"               "Mangaluru"          
 [43] "Solapur"             "Vijayapura"          "Akola"               "Latur"               "Kannur"              "Davanagere"          "Thalassery"         
 [50] "Amravati"            "Kalaburagi"          "Kozhikode"           "Guruvayur"           "Malappuram"          "Lucknow"             "Thrissur"           
 [57] "Mysuru"              "Kochi"               "Alappuzha"           "Nagpur"              "Kollam"              "Jabalpur"            "Ettumanoor"         
 [64] "Hyderabad"           "Coimbatore"          "Bengaluru"           "Thiruvananthapuram"  "Tiruppur"            "Faizabad"            "Erode"              
 [71] "Prayagraj"           "Pratapgarh"          "Salem"               "Dindigul"            "Madurai"             "Tiruchirappalli"     "Durg"               
 [78] "Vellore"             "Tirupati"            "Raipur"              "Bilaspur"            "Vijayawada"          "Puducherry"          "Chennai"            
 [85] "Kathmandu"           "Colombo"             "Rajamahendravaram"   "Patna"               "Kandy"               "Bihar Sharif"        "Visakhapatnam"      
 [92] "Ranchi"              "Brahmapur"           "Jamshedpur"          "Darjeeling"          "Siliguri"            "Cuttack"             "Bhubaneshwar"       
 [99] "Jalpaiguri"          "Berhampore"          "Kolkata"             "Krishnanagar"        "Guwahati [Dispur]"   "Agartala"            "Silchar"            
[106] "Dimapur"             "Bangkok"             "George Town"         "Kuala Lumpur"        "Phnom Penh"          "Singapore"           "Hong Kong"          
[113] "Sha Tin"             "Hsinchu"             "Taichung"            "New Taipei [Taipei]" "Tainan"              "Denpasar"            "Kaohsiung"          
[120] "Kota Kinabalu"      

attr(,"na.action")
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
  1  56  81  87  90  92  94  96  98 100 102 103 104 106 112 115 116 117 118 119 121 215 
attr(,"class")
[1] "omit"
indomalayan_cities_nmds = community_nmds(communities %>% filter(city_id %in% indomalayan_cities_community_data$city_id)) 
Run 0 stress 0.1190668 
Run 1 stress 0.1224401 
Run 2 stress 0.1394961 
Run 3 stress 0.1161598 
... New best solution
... Procrustes: rmse 0.02798297  max resid 0.2346031 
Run 4 stress 0.1540252 
Run 5 stress 0.1175538 
Run 6 stress 0.1167657 
Run 7 stress 0.1384412 
Run 8 stress 0.1241275 
Run 9 stress 0.1163498 
... Procrustes: rmse 0.025336  max resid 0.2481842 
Run 10 stress 0.1153501 
... New best solution
... Procrustes: rmse 0.008287738  max resid 0.08217213 
Run 11 stress 0.1199235 
Run 12 stress 0.1172201 
Run 13 stress 0.151756 
Run 14 stress 0.1246307 
Run 15 stress 0.134023 
Run 16 stress 0.1580175 
Run 17 stress 0.1229198 
Run 18 stress 0.1502633 
Run 19 stress 0.1189366 
Run 20 stress 0.117072 
Run 21 stress 0.1458634 
Run 22 stress 0.137476 
Run 23 stress 0.1186188 
Run 24 stress 0.1637221 
Run 25 stress 0.1191928 
Run 26 stress 0.1164559 
Run 27 stress 0.142366 
Run 28 stress 0.1379337 
Run 29 stress 0.1191853 
Run 30 stress 0.1266232 
*** Best solution was not repeated -- monoMDS stopping criteria:
    29: stress ratio > sratmax
     1: scale factor of the gradient < sfgrmin
indomalayan_cities_nmds
scree_plot(indomalayan_cities_nmds)

indomalayan_cities = cluster_cities(city_nmds = indomalayan_cities_nmds, cities_community_data = indomalayan_cities_community_data, centers = 5)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(indomalayan_cities)

indomalayan_biomes = resolve[resolve$REALM == 'Indomalayan',c('REALM')]
 
ggplot() + 
  geom_sf(data = indomalayan_biomes, aes(geometry = geometry)) + 
  geom_sf(data = indomalayan_cities, aes(geometry = geometry, color = cluster))
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_clusters.jpg'))
Saving 7.29 x 4.51 in image

Indomalayan Cluster 1

indomalayan_cluster1 = indomalayan_cities %>% filter(cluster == 1)
plot_city_cluster(afrotropic_cluster2, 'Indomalayan cluster 1')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster1.jpg'), width = get_image_width(indomalayan_cluster1), height = get_image_height(indomalayan_cluster1), units = "mm")

Indomalayan Cluster 2

indomalayan_cluster2 = indomalayan_cities %>% filter(cluster == 2)
plot_city_cluster(afrotropic_cluster2, 'Indomalayan cluster 2')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster2.jpg'), width = get_image_width(indomalayan_cluster2), height = get_image_height(indomalayan_cluster2), units = "mm")

Indomalayan Cluster 3

indomalayan_cluster3 = indomalayan_cities %>% filter(cluster == 3)
plot_city_cluster(afrotropic_cluster2, 'Indomalayan cluster 3')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster3.jpg'), width = get_image_width(indomalayan_cluster3), height = get_image_height(indomalayan_cluster3), units = "mm")

Indomalayan Cluster 4

indomalayan_cluster4 = indomalayan_cities %>% filter(cluster == 4)
plot_city_cluster(afrotropic_cluster2, 'Indomalayan cluster 4')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster4.jpg'), width = get_image_width(indomalayan_cluster4), height = get_image_height(indomalayan_cluster4), units = "mm")

Indomalayan Cluster 5

indomalayan_cluster5 = indomalayan_cities %>% filter(cluster == 5)
plot_city_cluster(afrotropic_cluster2, 'Indomalayan cluster 5')
ggsave(filename(REGION_DEEP_DIVE_FIGURES_OUTPUT, 'indomalayan_cluster5.jpg'), width = get_image_width(indomalayan_cluster5), height = get_image_height(indomalayan_cluster5), units = "mm")

LS0tCnRpdGxlOiAiRGVlcCBkaXZlIHJlZ2lvbmFsIGNvbW11bml0aWVzIC0gdXNpbmcgcmVsYXRpdmUgYWJ1bmRhbmNlIHByb3giCm91dHB1dDogaHRtbF9ub3RlYm9vawpiaWJsaW9ncmFwaHk6IC4uL3JlZi5iaWIgIAotLS0KCmBgYHtyfQpzb3VyY2UoJy4uL2Vudi5SJykKYGBgCgojIFNwZWNpZXMgaW4gY29tbXVuaXRpZXMKSXQgc2VlbXMgcmVhc29uYWJsZSB0byBleHBlY3QgdGhhdCBjaXRpZXMgd2l0aCBzaW1pYWxyIHJlZ2lvbmFsIHBvb2xzIHdpbGwgaGF2ZSBzaW1pbGFyIHNwZWNpZXMgZW50ZXJpbmcgdGhlIGNpdHksIGFuZCB0aHVzIGEgc2ltaWxhciByZXNwb25zZSB0byB1cmJhbmlzYXRpb24uCgojIyBMb2FkIGRhdGEKYGBge3J9CmNpdHlfZWZmb3J0ID0gcmVhZF9jc3YoZmlsZW5hbWUoQ0lUWV9EQVRBX09VVFBVVF9ESVIsICdjaXR5X2VmZm9ydC5jc3YnKSkKY2l0eV9lZmZvcnQKYGBgCgpgYGB7cn0KY29tbXVuaXRpZXMgPSByZWFkX2NzdihmaWxlbmFtZShDT01NVU5JVFlfT1VUUFVUX0RJUiwgJ2NvbW11bml0aWVzX2Zvcl9hbmFseXNpcy5jc3YnKSkKCmNvbW11bml0aWVzX3N1bW1hcnkgPSBjb21tdW5pdGllcyAlPiUgZ3JvdXBfYnkoY2l0eV9pZCkgJT4lIHN1bW1hcmlzZSgKICByZWdpb25hbF9wb29sX3NpemUgPSBuKCksIAogIHVyYmFuX3Bvb2xfc2l6ZSA9IHN1bShyZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkgPiAwKQopICU+JSBsZWZ0X2pvaW4oY2l0eV9lZmZvcnQgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgcGVyY2VudGFnZV90b3RhbF9jaXR5X2FyZWFfc3VydmV5ZWQpKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY29tbXVuaXRpZXMgJT4lIGZpbHRlcihyZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkgPiAwKSwgYWVzKHggPSByZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkpKSArIGdlb21fYmFyKHN0YXQgPSAiYmluIikKYGBgCgpgYGB7cn0KY2l0eV9wb2ludHMgPSBzdF9jZW50cm9pZChyZWFkX3NmKGZpbGVuYW1lKENJVFlfREFUQV9PVVRQVVRfRElSLCAnY2l0eV9zZWxlY3Rpb24uc2hwJykpKQpgYGAKCmBgYHtyfQpjb21tdW5pdHlfZGF0YV9tZXRyaWNzID0gcmVhZF9jc3YoZmlsZW5hbWUoQ09NTVVOSVRZX09VVFBVVF9ESVIsICdjb21tdW5pdHlfYXNzZW1ibHlfbWV0cmljc191c2luZ19yZWxhdGl2ZV9hYnVuZGFuY2UuY3N2JykpICU+JQogIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgbW50ZF9ub3JtYWxpc2VkLCBmZGl2X25vcm1hbGlzZWQsIG1hc3NfZmRpdl9ub3JtYWxpc2VkLCBsb2NvbW90b3J5X3RyYWl0X2ZkaXZfbm9ybWFsaXNlZCwgdHJvcGhpY190cmFpdF9mZGl2X25vcm1hbGlzZWQsIGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKSAlPiUKICBsZWZ0X2pvaW4ocmVhZF9jc3YoZmlsZW5hbWUoQ0lUWV9EQVRBX09VVFBVVF9ESVIsICdyZWFsbXMuY3N2JykpKSAlPiUKICBsZWZ0X2pvaW4oY29tbXVuaXRpZXNfc3VtbWFyeSkgJT4lCiAgbGVmdF9qb2luKGNpdHlfcG9pbnRzWyxjKCdjaXR5X2lkJywgJ2NpdHlfbm0nKV0pICU+JQogIHJlbmFtZShjaXR5X25hbWU9J2NpdHlfbm0nKSAlPiUKICBuYS5vbWl0KCkgJT4lCiAgYXJyYW5nZShjaXR5X2lkKQoKY29tbXVuaXR5X2RhdGFfbWV0cmljcwpgYGAKCkxvYWQgdHJhaXQgZGF0YQpgYGB7cn0KdHJhaXRzID0gcmVhZF9jc3YoZmlsZW5hbWUoVEFYT05PTVlfT1VUUFVUX0RJUiwgJ3RyYWl0c19qZXR6LmNzdicpKQpoZWFkKHRyYWl0cykKYGBgCgpgYGB7cn0KZmV0Y2hfbm9ybWFsaXNlZF90cmFpdHMgPSBmdW5jdGlvbihyZXF1aXJlZF9zcGVjaWVzX2xpc3QpIHsKICByZXF1aXJlZF90cmFpdHMgPSB0cmFpdHMgJT4lIGZpbHRlcihqZXR6X3NwZWNpZXNfbmFtZSAlaW4lIHJlcXVpcmVkX3NwZWNpZXNfbGlzdCkKICAKICByZXF1aXJlZF90cmFpdHMkZ2FwZV93aWR0aF9ub3JtYWxpc2VkID0gbm9ybWFsaXNlKHJlcXVpcmVkX3RyYWl0cyRnYXBlX3dpZHRoLCBtaW4ocmVxdWlyZWRfdHJhaXRzJGdhcGVfd2lkdGgpLCBtYXgocmVxdWlyZWRfdHJhaXRzJGdhcGVfd2lkdGgpKQogIHJlcXVpcmVkX3RyYWl0cyR0cm9waGljX3RyYWl0X25vcm1hbGlzZWQgPSBub3JtYWxpc2UocmVxdWlyZWRfdHJhaXRzJHRyb3BoaWNfdHJhaXQsIG1pbihyZXF1aXJlZF90cmFpdHMkdHJvcGhpY190cmFpdCksIG1heChyZXF1aXJlZF90cmFpdHMkdHJvcGhpY190cmFpdCkpCiAgcmVxdWlyZWRfdHJhaXRzJGxvY29tb3RvcnlfdHJhaXRfbm9ybWFsaXNlZCA9IG5vcm1hbGlzZShyZXF1aXJlZF90cmFpdHMkbG9jb21vdG9yeV90cmFpdCwgbWluKHJlcXVpcmVkX3RyYWl0cyRsb2NvbW90b3J5X3RyYWl0KSwgbWF4KHJlcXVpcmVkX3RyYWl0cyRsb2NvbW90b3J5X3RyYWl0KSkKICByZXF1aXJlZF90cmFpdHMkbWFzc19ub3JtYWxpc2VkID0gbm9ybWFsaXNlKHJlcXVpcmVkX3RyYWl0cyRtYXNzLCBtaW4ocmVxdWlyZWRfdHJhaXRzJG1hc3MpLCBtYXgocmVxdWlyZWRfdHJhaXRzJG1hc3MpKQogIAogIHRyYWl0c19ub3JtYWxpc2VkX2xvbmcgPSByZXF1aXJlZF90cmFpdHMgJT4lIHBpdm90X2xvbmdlcihjb2xzID0gYygnZ2FwZV93aWR0aF9ub3JtYWxpc2VkJywgJ3Ryb3BoaWNfdHJhaXRfbm9ybWFsaXNlZCcsICdsb2NvbW90b3J5X3RyYWl0X25vcm1hbGlzZWQnLCAnbWFzc19ub3JtYWxpc2VkJyksIG5hbWVzX3RvID0gJ3RyYWl0JywgdmFsdWVzX3RvID0gJ25vcm1hbGlzZWRfdmFsdWUnKSAlPiUgZHBseXI6OnNlbGVjdChqZXR6X3NwZWNpZXNfbmFtZSwgdHJhaXQsIG5vcm1hbGlzZWRfdmFsdWUpCiAgdHJhaXRzX25vcm1hbGlzZWRfbG9uZyR0cmFpdCA9IGZhY3Rvcih0cmFpdHNfbm9ybWFsaXNlZF9sb25nJHRyYWl0LCBsZXZlbHMgPSBjKCdnYXBlX3dpZHRoX25vcm1hbGlzZWQnLCAndHJvcGhpY190cmFpdF9ub3JtYWxpc2VkJywgJ2xvY29tb3RvcnlfdHJhaXRfbm9ybWFsaXNlZCcsICdtYXNzX25vcm1hbGlzZWQnKSwgbGFiZWxzID0gYygnR2FwZSBXaWR0aCcsICdUcm9waGljIFRyYWl0JywgJ0xvY29tb3RvcnkgVHJhaXQnLCAnTWFzcycpKQogIAogIHRyYWl0c19ub3JtYWxpc2VkX2xvbmcKfQoKZmV0Y2hfbm9ybWFsaXNlZF90cmFpdHMoYygnQXBsb3BlbGlhX2xhcnZhdGEnLCAnQ2hhbGNvcGhhcHNfaW5kaWNhJywgJ0NhbG9lbmFzX25pY29iYXJpY2EnKSkKYGBgCgoKUmVhZCBpbiBvdXIgcGh5bG9nZW55CmBgYHtyfQpwaHlsb190cmVlID0gcmVhZC50cmVlKGZpbGVuYW1lKFRBWE9OT01ZX09VVFBVVF9ESVIsICdwaHlsb2dlbnkudHJlJykpCmdndHJlZShwaHlsb190cmVlLCBsYXlvdXQ9J2NpcmN1bGFyJykKYGBgCgpMb2FkIHJlc29sdmUgZWNvcmVnaW9ucwpgYGB7cn0KcmVzb2x2ZSA9IHJlYWRfcmVzb2x2ZSgpCmBgYAoKIyMgQ3JlYXRlIGhlbHBlciBmdW5jdGlvbnMKYGBge3J9CnRvX3NwZWNpZXNfbWF0cml4ID0gZnVuY3Rpb24oZmlsdGVyZWRfY29tbXVuaXRpZXMpIHsKICBmaWx0ZXJlZF9jb21tdW5pdGllcyAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KGNpdHlfaWQsIGpldHpfc3BlY2llc19uYW1lKSAlPiUgCiAgICBkaXN0aW5jdCgpICU+JQogICAgbXV0YXRlKHByZXNlbnQgPSBUUlVFKSAlPiUgCiAgICBwaXZvdF93aWRlcigKICAgICAgbmFtZXNfZnJvbSA9IGpldHpfc3BlY2llc19uYW1lLCAKICAgICAgdmFsdWVzX2Zyb20gPSAicHJlc2VudCIsIAogICAgICB2YWx1ZXNfZmlsbCA9IGxpc3QocHJlc2VudCA9IEYpCiAgICApICU+JSAKICAgIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKHZhcj0nY2l0eV9pZCcpCn0KYGBgCgpgYGB7cn0KY29tbXVuaXR5X25tZHMgPSBmdW5jdGlvbihmaWx0ZXJlZF9jb21tdW5pdGllcykgewogIHNwZWNpZXNfbWF0cml4ID0gdG9fc3BlY2llc19tYXRyaXgoZmlsdGVyZWRfY29tbXVuaXRpZXMpCiAgbm1kcyA8LSBtZXRhTURTKHNwZWNpZXNfbWF0cml4LCBrPTIsIHRyeW1heCA9IDMwKQogIG5tZHNfcmVzdWx0ID0gZGF0YS5mcmFtZSh2ZWdhbjo6c2NvcmVzKG5tZHMpJHNpdGVzKQogIG5tZHNfcmVzdWx0JGNpdHlfaWQgPSBhcy5kb3VibGUocm93bmFtZXMobm1kc19yZXN1bHQpKQogIHJvd25hbWVzKG5tZHNfcmVzdWx0KSA9IE5VTEwKICBubWRzX3Jlc3VsdAp9CmBgYAoKaHR0cHM6Ly93d3cuZGF0YWNhbXAuY29tL3R1dG9yaWFsL2stbWVhbnMtY2x1c3RlcmluZy1yCmBgYHtyfQpzY3JlZV9wbG90ID0gZnVuY3Rpb24oY29tbXVuaXR5X25tZHNfZGF0YSkgewogICMgRGVjaWRlIGhvdyBtYW55IGNsdXN0ZXJzIHRvIGxvb2sgYXQKICBuX2NsdXN0ZXJzIDwtIG1pbigxMCwgbnJvdyhjb21tdW5pdHlfbm1kc19kYXRhKSAtIDEpCiAgCiAgIyBJbml0aWFsaXplIHRvdGFsIHdpdGhpbiBzdW0gb2Ygc3F1YXJlcyBlcnJvcjogd3NzCiAgd3NzIDwtIG51bWVyaWMobl9jbHVzdGVycykKICAKICBzZXQuc2VlZCgxMjMpCiAgCiAgIyBMb29rIG92ZXIgMSB0byBuIHBvc3NpYmxlIGNsdXN0ZXJzCiAgZm9yIChpIGluIDE6bl9jbHVzdGVycykgewogICAgIyBGaXQgdGhlIG1vZGVsOiBrbS5vdXQKICAgIGttLm91dCA8LSBrbWVhbnMoY29tbXVuaXR5X25tZHNfZGF0YVssYygnTk1EUzEnLCdOTURTMicpXSwgY2VudGVycyA9IGksIG5zdGFydCA9IDIwKQogICAgIyBTYXZlIHRoZSB3aXRoaW4gY2x1c3RlciBzdW0gb2Ygc3F1YXJlcwogICAgd3NzW2ldIDwtIGttLm91dCR0b3Qud2l0aGluc3MKICB9CiAgCiAgIyBQcm9kdWNlIGEgc2NyZWUgcGxvdAogIHdzc19kZiA8LSB0aWJibGUoY2x1c3RlcnMgPSAxOm5fY2x1c3RlcnMsIHdzcyA9IHdzcykKICAgCiAgc2NyZWVfcGxvdCA8LSBnZ3Bsb3Qod3NzX2RmLCBhZXMoeCA9IGNsdXN0ZXJzLCB5ID0gd3NzLCBncm91cCA9IDEpKSArCiAgICAgIGdlb21fcG9pbnQoc2l6ZSA9IDQpICsKICAgICAgZ2VvbV9saW5lKCkgKwogICAgICBnZW9tX2hsaW5lKGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvciA9ICJvcmFuZ2UiLCB5aW50ZXJjZXB0ID0gd3NzKSArCiAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBjKDIsIDQsIDYsIDgsIDEwKSkgKwogICAgICB4bGFiKCdOdW1iZXIgb2YgY2x1c3RlcnMnKQogIHNjcmVlX3Bsb3QKfQpgYGAKCmBgYHtyfQpjbHVzdGVyX2NpdGllcyA9IGZ1bmN0aW9uKGNpdHlfbm1kcywgY2l0aWVzX2NvbW11bml0eV9kYXRhLCBjZW50ZXJzKSB7CiAgc2V0LnNlZWQoMTIzKQogIGttZWFuc19jbHVzdGVycyA8LSBrbWVhbnMoY2l0eV9ubWRzWyxjKCdOTURTMScsICdOTURTMicpXSwgY2VudGVycyA9IGNlbnRlcnMsIG5zdGFydCA9IDIwKQogIGNpdHlfbm1kcyRjbHVzdGVyID0ga21lYW5zX2NsdXN0ZXJzJGNsdXN0ZXIKICBjaXRpZXNfY29tbXVuaXR5X2RhdGEgJT4lIGxlZnRfam9pbihjaXR5X25tZHMpICU+JSBtdXRhdGUoY2x1c3RlciA9IGFzLmZhY3RvcihjbHVzdGVyKSkKfQpgYGAKCmBgYHtyfQpwbG90X25tZHNfY2x1c3RlcnMgPSBmdW5jdGlvbihjbHVzdGVyX2NpdGllcykgewogIGNsdXN0ZXJfY2l0aWVzICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfaWQsIGNpdHlfbmFtZSwgTk1EUzEsIE5NRFMyLCBjbHVzdGVyKSAlPiUgZGlzdGluY3QoKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBOTURTMSwgeSA9IE5NRFMyLCBjb2xvdXIgPSBjbHVzdGVyKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX2xhYmVsX3JlcGVsKGFlcyhsYWJlbCA9IGNpdHlfbmFtZSkpCn0KYGBgCgpgYGB7cn0KZ2V0X3ByZXNlbmNlX2NlbGxfd2lkdGggPSBmdW5jdGlvbihjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzKSB7CiAgMTAgKiBsZW5ndGgodW5pcXVlKGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MkY2l0eV9pZCkpCn0KCmdldF9wcmVzZW5jZV9jZWxsX2hlaWdodCA9IGZ1bmN0aW9uKGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpIHsKICBzcGVjaWVzID0gc3BlY2llc19pbl9jbHVzdGVyID0gY29tbXVuaXRpZXMgJT4lIAogICAgZmlsdGVyKGNpdHlfaWQgJWluJSBjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzJGNpdHlfaWQpICU+JSAKICAgIGRwbHlyOjpzZWxlY3QoamV0el9zcGVjaWVzX25hbWUpICU+JSAKICAgIGRpc3RpbmN0KCkKICAKICAxMCAqIG5yb3coc3BlY2llcykKfQoKY2l0eV9tZXRyaWNfaGVpZ2h0ID0gMzAKdHJhaXRzX3dpZHRoID0gNTAKcGh5bG9fdHJlZV93aWR0aCA9IDEyNQp0aXRsZV9oZWlnaHQgPSA4CgpnZXRfaW1hZ2VfaGVpZ2h0ID0gZnVuY3Rpb24oY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcykgewogIGdldF9wcmVzZW5jZV9jZWxsX2hlaWdodChjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzKSArICgyICogY2l0eV9tZXRyaWNfaGVpZ2h0KSArIHRpdGxlX2hlaWdodAp9CgpnZXRfaW1hZ2Vfd2lkdGggPSBmdW5jdGlvbihjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzKSB7CiAgZ2V0X3ByZXNlbmNlX2NlbGxfd2lkdGgoY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcykgKyB0cmFpdHNfd2lkdGggKyBwaHlsb190cmVlX3dpZHRoCn0KYGBgCiAgICAgICAgIApgYGB7cn0KdGVzdF92YWx1ZSA9IGZ1bmN0aW9uKG5hbWUsIG5vcm1hbGlzZWRfbGlzdCkgewogIHdpbGNveF90ZXN0X3Jlc3VsdCA9IHdpbGNveC50ZXN0KG5vcm1hbGlzZWRfbGlzdCwgbXUgPSAwLjUpCiAgCiAgc2lnbmlmaWNhbmNlID0gaWZlbHNlKHdpbGNveF90ZXN0X3Jlc3VsdCRwLnZhbHVlIDwgMC4wMDAxLCAnKioqJywgCiAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSh3aWxjb3hfdGVzdF9yZXN1bHQkcC52YWx1ZSA8IDAuMDAxLCAnKionLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSh3aWxjb3hfdGVzdF9yZXN1bHQkcC52YWx1ZSA8IDAuMDEsICcqJywgJycpKSkKICBtID0gbWVhbihub3JtYWxpc2VkX2xpc3QpCiAgCiAgcGFzdGUobmFtZSwgJ21lYW4nLCByb3VuZChtLCAyKSwgc2lnbmlmaWNhbmNlKQp9CmBgYAoKYGBge3J9CnBsb3RfY2l0eV9jbHVzdGVyID0gZnVuY3Rpb24oY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcywgdGl0bGUpIHsKICBzcGVjaWVzX2luX2NsdXN0ZXIgPSBjb21tdW5pdGllcyAlPiUgCiAgICBmaWx0ZXIoY2l0eV9pZCAlaW4lIGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MkY2l0eV9pZCkgJT4lIAogICAgZHBseXI6OnNlbGVjdChqZXR6X3NwZWNpZXNfbmFtZSwgY2l0eV9uYW1lLCByZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkpCiAgCiAgdHJlZV9jcm9wcGVkIDwtIGxhZGRlcml6ZShkcm9wLnRpcChwaHlsb190cmVlLCBzZXRkaWZmKHBoeWxvX3RyZWUkdGlwLmxhYmVsLCBzcGVjaWVzX2luX2NsdXN0ZXIkamV0el9zcGVjaWVzX25hbWUpKSkKICAgIAogIGdnX3RyZWUgPSBnZ3RyZWUodHJlZV9jcm9wcGVkKQogIAogIGdnX3ByZXNlbmNlID0gZ2dwbG90KHNwZWNpZXNfaW5fY2x1c3RlciwgYWVzKHg9Y2l0eV9uYW1lLCB5PWpldHpfc3BlY2llc19uYW1lKSkgKyAKICAgICAgICAgIGdlb21fdGlsZShhZXMoZmlsbD1yZWxhdGl2ZV9hYnVuZGFuY2VfcHJveHkpKSArIAogICAgICAgICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3Vycz1jKCIjOThGQjk4IiwgIiNGRkZGRTAiLCAieWVsbG93IiwgIm9yYW5nZSIsICIjRkY0NTAwIiwgInJlZCIsICJyZWQiKSwgdmFsdWVzPWMoMCwgMC4wMDAwMDAwMDAwMSwgMC4xLCAwLjI1LCAwLjUsIDAuNzUsIDEpLCBuYS52YWx1ZSA9ICJ0cmFuc3BhcmVudCIpICsKICAgICAgICAgIHRoZW1lX21pbmltYWwoKSArIHhsYWIoTlVMTCkgKyB5bGFiKE5VTEwpICsgCiAgICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpICsgCiAgICAgICAgICBsYWJzKGZpbGw9J1VyYmFuIFByb3h5IEFidW5kYW5jZScpCiAgCiAgc3BlY2llc19pbl9jbHVzdGVyX3RyYWl0cyA9IGZldGNoX25vcm1hbGlzZWRfdHJhaXRzKHNwZWNpZXNfaW5fY2x1c3RlciRqZXR6X3NwZWNpZXNfbmFtZSkKICAKICBnZ190cmFpdHMgPSBnZ3Bsb3Qoc3BlY2llc19pbl9jbHVzdGVyX3RyYWl0cywgYWVzKHggPSB0cmFpdCwgeSA9IGpldHpfc3BlY2llc19uYW1lLCBzaXplID0gbm9ybWFsaXNlZF92YWx1ZSkpICsgZ2VvbV9wb2ludCgpICsgdGhlbWVfbWluaW1hbCgpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpLCBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCkpICsgeGxhYihOVUxMKSArIHlsYWIoTlVMTCkgKyBsYWJzKHNpemUgPSAiTm9ybWFsaXNlZCBWYWx1ZSIpCiAgCiAgZ2dfY2l0aWVzX21udGQgPSBnZ3Bsb3QoY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcywgYWVzKHggPSBjaXR5X25hbWUsIHkgPSBtbnRkX25vcm1hbGlzZWQpKSArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpKSArIHhsYWIoTlVMTCkgKyB5bGFiKCJNTlREIikgKyB5bGltKDAsIDEpCiAgCiAgZ2dfY2l0aWVzX2ZkID0gZ2dwbG90KGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MsIGFlcyh4ID0gY2l0eV9uYW1lLCB5ID0gZmRpdl9ub3JtYWxpc2VkKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSkgKyB4bGFiKE5VTEwpICsgeWxhYigiRkRpdiIpICsgeWxpbSgwLCAxKQogIAogIGdnX3RpdGxlID0gZ2dwbG90KCkgKyBsYWJzKHRpdGxlID0gdGl0bGUsIHN1YnRpdGxlID0gcGFzdGUoCiAgICB0ZXN0X3ZhbHVlKCdNTlREJywgY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcyRtbnRkX25vcm1hbGlzZWQpLAogICAgdGVzdF92YWx1ZSgnRkRpdicsIGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MkZmRpdl9ub3JtYWxpc2VkKSwKICAgIHRlc3RfdmFsdWUoJ0xvY29tb3RvcnkgdHJhaXQnLCBjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzJGxvY29tb3RvcnlfdHJhaXRfZmRpdl9ub3JtYWxpc2VkKSwKICAgIHRlc3RfdmFsdWUoJ0dhcGUgd2lkdGgnLCBjaXR5X2NsdXN0ZXJfZGF0YV9tZXRyaWNzJGdhcGVfd2lkdGhfZmRpdl9ub3JtYWxpc2VkKSwKICAgIHNlcCA9ICdcbicKICApKSArIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3Quc3VidGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OCwgaGp1c3Q9MCwgY29sb3I9IiM0NDQ0NDQiKSkKICAKICBnZ19wcmVzZW5jZV9oZWlnaHQgPSBnZXRfcHJlc2VuY2VfY2VsbF9oZWlnaHQoY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcykKICBnZ19wcmVzZW5jZV93aWR0aCA9IGdldF9wcmVzZW5jZV9jZWxsX3dpZHRoKGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpCiAgCiAgZ2dfcHJlc2VuY2UgJT4lIGluc2VydF90b3AoZ2dfY2l0aWVzX21udGQsIGhlaWdodCA9IChjaXR5X21ldHJpY19oZWlnaHQgLyBnZ19wcmVzZW5jZV9oZWlnaHQpKSAlPiUgaW5zZXJ0X3RvcChnZ19jaXRpZXNfZmQsIGhlaWdodCA9IChjaXR5X21ldHJpY19oZWlnaHQgLyBnZ19wcmVzZW5jZV9oZWlnaHQpKSAlPiUgaW5zZXJ0X2xlZnQoZ2dfdHJlZSwgd2lkdGggPSAocGh5bG9fdHJlZV93aWR0aCAvIGdnX3ByZXNlbmNlX3dpZHRoKSkgJT4lIGluc2VydF9yaWdodChnZ190cmFpdHMsIHdpZHRoID0gKHRyYWl0c193aWR0aCAvIGdnX3ByZXNlbmNlX3dpZHRoKSkgJT4lIGluc2VydF90b3AoZ2dfdGl0bGUsIGhlaWdodCA9ICh0aXRsZV9oZWlnaHQgLyBnZ19wcmVzZW5jZV9oZWlnaHQpKQp9CmBgYAoKYGBge3J9ClJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQgPSBta2RpcihGSUdVUkVTX09VVFBVVF9ESVIsICdhcHBlbmRpeF9yZWdpb25hbF9kZWVwX2RpdmVfdXNpbmdfYWJ1bmRhbmNlJykKYGBgCgojIyBOZWFyY3RpYwpgYGB7cn0KbmVhcmN0aWNfY2l0aWVzX2NvbW11bml0eV9kYXRhID0gY29tbXVuaXR5X2RhdGFfbWV0cmljcyAlPiUgZmlsdGVyKGNvcmVfcmVhbG0gPT0gJ05lYXJjdGljJykKbmVhcmN0aWNfY2l0aWVzX2NvbW11bml0eV9kYXRhICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfbmFtZSkgJT4lIGRpc3RpbmN0KCkgJT4lIGFzLmxpc3QoKQpgYGAKCmBgYHtyfQpuZWFyY3RpY19jaXRpZXNfbm1kcyA9IGNvbW11bml0eV9ubWRzKGNvbW11bml0aWVzICU+JSBmaWx0ZXIoY2l0eV9pZCAlaW4lIG5lYXJjdGljX2NpdGllc19jb21tdW5pdHlfZGF0YSRjaXR5X2lkKSkgCm5lYXJjdGljX2NpdGllc19ubWRzCmBgYAoKYGBge3J9CnNjcmVlX3Bsb3QobmVhcmN0aWNfY2l0aWVzX25tZHMpCmBgYAoKYGBge3J9Cm5lYXJjdGljX2NpdGllcyA9IGNsdXN0ZXJfY2l0aWVzKGNpdHlfbm1kcyA9IG5lYXJjdGljX2NpdGllc19ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBuZWFyY3RpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEsIGNlbnRlcnMgPSA0KQpgYGAKCmBgYHtyfQpwbG90X25tZHNfY2x1c3RlcnMobmVhcmN0aWNfY2l0aWVzKQpgYGAKCmBgYHtyfQpuZWFyY3RpY19iaW9tZXMgPSBzdF9jcm9wKHJlc29sdmVbcmVzb2x2ZSRSRUFMTSA9PSAnTmVhcmN0aWMnLGMoJ1JFQUxNJyldLCB4bWluID0gLTIyMCwgeW1pbiA9IDAsIHhtYXggPSAwLCB5bWF4ID0gNzApCiAKZ2dwbG90KCkgKyAKICBnZW9tX3NmKGRhdGEgPSBuZWFyY3RpY19iaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBuZWFyY3RpY19jaXRpZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5LCBjb2xvciA9IGNsdXN0ZXIpKQoKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZWFydGljX2NsdXN0ZXJzLmpwZycpKQpgYGAKCgojIyMgTmVhcnRpYyBDbHVzdGVyIDFgCmBgYHtyfQpuZWFyYWN0aWNfY2x1c3RlcjEgPSBuZWFyY3RpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDEpCnBsb3RfY2l0eV9jbHVzdGVyKG5lYXJhY3RpY19jbHVzdGVyMSwgJ05lYXJ0aWMgY2x1c3RlciAxJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZWFydGljX2NsdXN0ZXIxLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChuZWFyYWN0aWNfY2x1c3RlcjEpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KG5lYXJhY3RpY19jbHVzdGVyMSksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgTmVhcnRpYyBDbHVzdGVyIDIKYGBge3J9Cm5lYXJhY3RpY19jbHVzdGVyMiA9IG5lYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMikKcGxvdF9jaXR5X2NsdXN0ZXIobmVhcmFjdGljX2NsdXN0ZXIyLCAnTmVhcnRpYyBjbHVzdGVyIDInKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ25lYXJ0aWNfY2x1c3RlcjIuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKG5lYXJhY3RpY19jbHVzdGVyMiksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQobmVhcmFjdGljX2NsdXN0ZXIyKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBOZWFydGljIENsdXN0ZXIgMwpgYGB7cn0KbmVhcmFjdGljX2NsdXN0ZXIzID0gbmVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKQpwbG90X2NpdHlfY2x1c3RlcihuZWFyYWN0aWNfY2x1c3RlcjMsICdOZWFydGljIGNsdXN0ZXIgMycpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVhcnRpY19jbHVzdGVyMy5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgobmVhcmFjdGljX2NsdXN0ZXIzKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChuZWFyYWN0aWNfY2x1c3RlcjMpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIE5lYXJ0aWMgQ2x1c3RlciA0CmBgYHtyfQpuZWFyYWN0aWNfY2x1c3RlcjQgPSBuZWFyY3RpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDQpCnBsb3RfY2l0eV9jbHVzdGVyKG5lYXJhY3RpY19jbHVzdGVyNCwgJ05lYXJ0aWMgY2x1c3RlciA0JykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZWFydGljX2NsdXN0ZXI0LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChuZWFyYWN0aWNfY2x1c3RlcjQpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KG5lYXJhY3RpY19jbHVzdGVyNCksIHVuaXRzID0gIm1tIikKYGBgCgojIyBOZW90cm9waWMKYGBge3J9Cm5lb3Ryb3BpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBjb21tdW5pdHlfZGF0YV9tZXRyaWNzICU+JSBmaWx0ZXIoY29yZV9yZWFsbSA9PSAnTmVvdHJvcGljJykKbmVvdHJvcGljX2NpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgZHBseXI6OnNlbGVjdChjaXR5X25hbWUpICU+JSBkaXN0aW5jdCgpICU+JSBhcy5saXN0KCkKYGBgCgpgYGB7cn0KbmVvdHJvcGljX2NpdGllc19ubWRzID0gY29tbXVuaXR5X25tZHMoY29tbXVuaXRpZXMgJT4lIGZpbHRlcihjaXR5X2lkICVpbiUgbmVvdHJvcGljX2NpdGllc19jb21tdW5pdHlfZGF0YSRjaXR5X2lkKSkgCm5lb3Ryb3BpY19jaXRpZXNfbm1kcwpgYGAKCmBgYHtyfQpzY3JlZV9wbG90KG5lb3Ryb3BpY19jaXRpZXNfbm1kcykKYGBgCgpgYGB7cn0KbmVvdHJvcGljX2NpdGllcyA9IGNsdXN0ZXJfY2l0aWVzKGNpdHlfbm1kcyA9IG5lb3Ryb3BpY19jaXRpZXNfbm1kcywgY2l0aWVzX2NvbW11bml0eV9kYXRhID0gbmVvdHJvcGljX2NpdGllc19jb21tdW5pdHlfZGF0YSwgY2VudGVycyA9IDUpCmBgYAoKYGBge3J9CnBsb3Rfbm1kc19jbHVzdGVycyhuZW90cm9waWNfY2l0aWVzKQpgYGAKCmBgYHtyfQpuZW90cm9waWNfYmlvbWVzID0gcmVzb2x2ZVtyZXNvbHZlJFJFQUxNID09ICdOZW90cm9waWMnLGMoJ1JFQUxNJyldCiAKZ2dwbG90KCkgKyAKICBnZW9tX3NmKGRhdGEgPSBuZW90cm9waWNfYmlvbWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsgCiAgZ2VvbV9zZihkYXRhID0gbmVvdHJvcGljX2NpdGllcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnksIGNvbG9yID0gY2x1c3RlcikpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnbmVvdHJvcGljX2NsdXN0ZXJzLmpwZycpKQpgYGAKCiMjIyBOZW90cm9waWMgQ2x1c3RlciAxCmBgYHtyfQpuZW90cm9waWNfY2x1c3RlcjEgPSBuZW90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAxKQpwbG90X2NpdHlfY2x1c3RlcihuZW90cm9waWNfY2x1c3RlcjEsICdOZW90cm9waWMgY2x1c3RlciAxJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZW90cm9waWNfY2x1c3RlcjEuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKG5lb3Ryb3BpY19jbHVzdGVyMSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQobmVvdHJvcGljX2NsdXN0ZXIxKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBOZW90cm9waWMgQ2x1c3RlciAyCmBgYHtyfQpuZW90cm9waWNfY2x1c3RlcjIgPSBuZW90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKQpwbG90X2NpdHlfY2x1c3RlcihuZW90cm9waWNfY2x1c3RlcjIsICdOZW90cm9waWMgY2x1c3RlciAyJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZW90cm9waWNfY2x1c3RlcjIuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKG5lb3Ryb3BpY19jbHVzdGVyMiksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQobmVvdHJvcGljX2NsdXN0ZXIyKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBOZW90cm9waWMgQ2x1c3RlciAzCmBgYHtyfQpuZW90cm9waWNfY2x1c3RlcjMgPSBuZW90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKQpwbG90X2NpdHlfY2x1c3RlcihuZW90cm9waWNfY2x1c3RlcjMsICdOZW90cm9waWMgY2x1c3RlciAzJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZW90cm9waWNfY2x1c3RlcjMuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKG5lb3Ryb3BpY19jbHVzdGVyMyksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQobmVvdHJvcGljX2NsdXN0ZXIzKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBOZW90cm9waWMgQ2x1c3RlciA0CmBgYHtyfQpuZW90cm9waWNfY2x1c3RlcjQgPSBuZW90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSA0KQpwbG90X2NpdHlfY2x1c3RlcihuZW90cm9waWNfY2x1c3RlcjQsICdOZW90cm9waWMgY2x1c3RlciA0JykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZW90cm9waWNfY2x1c3RlcjQuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKG5lb3Ryb3BpY19jbHVzdGVyNCksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQobmVvdHJvcGljX2NsdXN0ZXI0KSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBOZW90cm9waWMgQ2x1c3RlciA1CmBgYHtyfQpuZW90cm9waWNfY2x1c3RlcjUgPSBuZW90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSA1KQpwbG90X2NpdHlfY2x1c3RlcihuZW90cm9waWNfY2x1c3RlcjUsICdOZW90cm9waWMgY2x1c3RlciA1JykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICduZW90cm9waWNfY2x1c3RlcjUuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKG5lb3Ryb3BpY19jbHVzdGVyNSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQobmVvdHJvcGljX2NsdXN0ZXI1KSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIFBhbGVhcmN0aWMKYGBge3J9CnBhbGVhcmN0aWNfY2l0aWVzX2NvbW11bml0eV9kYXRhID0gY29tbXVuaXR5X2RhdGFfbWV0cmljcyAlPiUgZmlsdGVyKGNvcmVfcmVhbG0gPT0gJ1BhbGVhcmN0aWMnKQpwYWxlYXJjdGljX2NpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgZHBseXI6OnNlbGVjdChjaXR5X25hbWUpICU+JSBkaXN0aW5jdCgpICU+JSBhcy5saXN0KCkKYGBgCgpgYGB7cn0KcGFsZWFyY3RpY19jaXRpZXNfbm1kcyA9IGNvbW11bml0eV9ubWRzKGNvbW11bml0aWVzICU+JSBmaWx0ZXIoY2l0eV9pZCAlaW4lIHBhbGVhcmN0aWNfY2l0aWVzX2NvbW11bml0eV9kYXRhJGNpdHlfaWQpKSAKcGFsZWFyY3RpY19jaXRpZXNfbm1kcwpgYGAKCmBgYHtyfQpzY3JlZV9wbG90KHBhbGVhcmN0aWNfY2l0aWVzX25tZHMpCmBgYAoKYGBge3J9CnBhbGVhcmN0aWNfY2l0aWVzID0gY2x1c3Rlcl9jaXRpZXMoY2l0eV9ubWRzID0gcGFsZWFyY3RpY19jaXRpZXNfbm1kcywgY2l0aWVzX2NvbW11bml0eV9kYXRhID0gcGFsZWFyY3RpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEsIGNlbnRlcnMgPSA3KQpgYGAKCmBgYHtyfQpwbG90X25tZHNfY2x1c3RlcnMocGFsZWFyY3RpY19jaXRpZXMpCmBgYAoKYGBge3J9CnBhbGVhcmN0aWNfYmlvbWVzID0gc3RfY3JvcChyZXNvbHZlW3Jlc29sdmUkUkVBTE0gPT0gJ1BhbGVhcmN0aWMnLGMoJ1JFQUxNJyldLCB4bWluID0gLTMwLCB5bWluID0gMjAsIHhtYXggPSA4MCwgeW1heCA9IDY1KQogCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gcGFsZWFyY3RpY19iaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBwYWxlYXJjdGljX2NpdGllcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnksIGNvbG9yID0gY2x1c3RlcikpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAncGFsZWFyY3RpY19jbHVzdGVycy5qcGcnKSkKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDEKYGBge3J9CnBhbGVhcmN0aWNfY2x1c3RlcjEgPSBwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkKcGxvdF9jaXR5X2NsdXN0ZXIocGFsZWFyY3RpY19jbHVzdGVyMSwgJ1BhbGVhcmN0aWMgY2x1c3RlciAxJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdwYWxlYXJjdGljX2NsdXN0ZXIxLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChwYWxlYXJjdGljX2NsdXN0ZXIxKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChwYWxlYXJjdGljX2NsdXN0ZXIxKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBQYWxlYXJjdGljIENsdXN0ZXIgMgpgYGB7cn0KcGFsZWFyY3RpY19jbHVzdGVyMiA9IHBhbGVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKQpwbG90X2NpdHlfY2x1c3RlcihwYWxlYXJjdGljX2NsdXN0ZXIyLCAnUGFsZWFyY3RpYyBjbHVzdGVyIDInKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ3BhbGVhcmN0aWNfY2x1c3RlcjIuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKHBhbGVhcmN0aWNfY2x1c3RlcjIpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KHBhbGVhcmN0aWNfY2x1c3RlcjIpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIFBhbGVhcmN0aWMgQ2x1c3RlciAzCmBgYHtyfQpwYWxlYXJjdGljX2NsdXN0ZXIzID0gcGFsZWFyY3RpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDMpCnBsb3RfY2l0eV9jbHVzdGVyKHBhbGVhcmN0aWNfY2x1c3RlcjMsICdQYWxlYXJjdGljIGNsdXN0ZXIgMycpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAncGFsZWFyY3RpY19jbHVzdGVyMy5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgocGFsZWFyY3RpY19jbHVzdGVyMyksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQocGFsZWFyY3RpY19jbHVzdGVyMyksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDQKYGBge3J9CnBhbGVhcmN0aWNfY2x1c3RlcjQgPSBwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNCkKcGxvdF9jaXR5X2NsdXN0ZXIocGFsZWFyY3RpY19jbHVzdGVyNCwgJ1BhbGVhcmN0aWMgY2x1c3RlciA0JykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdwYWxlYXJjdGljX2NsdXN0ZXI0LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChwYWxlYXJjdGljX2NsdXN0ZXI0KSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChwYWxlYXJjdGljX2NsdXN0ZXI0KSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBQYWxlYXJjdGljIENsdXN0ZXIgNQpgYGB7cn0KcGFsZWFyY3RpY19jbHVzdGVyNSA9IHBhbGVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSA1KQpwbG90X2NpdHlfY2x1c3RlcihwYWxlYXJjdGljX2NsdXN0ZXI1LCAnUGFsZWFyY3RpYyBjbHVzdGVyIDUnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ3BhbGVhcmN0aWNfY2x1c3RlcjUuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKHBhbGVhcmN0aWNfY2x1c3RlcjUpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KHBhbGVhcmN0aWNfY2x1c3RlcjUpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIFBhbGVhcmN0aWMgQ2x1c3RlciA2CmBgYHtyfQpwYWxlYXJjdGljX2NsdXN0ZXI2ID0gcGFsZWFyY3RpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDYpCnBsb3RfY2l0eV9jbHVzdGVyKHBhbGVhcmN0aWNfY2x1c3RlcjYsICdQYWxlYXJjdGljIGNsdXN0ZXIgNicpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAncGFsZWFyY3RpY19jbHVzdGVyNi5qcGcnKSwgd2lkdGggPSBnZXRfaW1hZ2Vfd2lkdGgocGFsZWFyY3RpY19jbHVzdGVyNiksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQocGFsZWFyY3RpY19jbHVzdGVyNiksIHVuaXRzID0gIm1tIikKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDcKYGBge3J9CnBhbGVhcmN0aWNfY2x1c3RlcjcgPSBwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gNykKcGxvdF9jaXR5X2NsdXN0ZXIocGFsZWFyY3RpY19jbHVzdGVyNywgJ1BhbGVhcmN0aWMgY2x1c3RlciA3JykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdwYWxlYXJjdGljX2NsdXN0ZXI3LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChwYWxlYXJjdGljX2NsdXN0ZXI3KSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChwYWxlYXJjdGljX2NsdXN0ZXI3KSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIEFmcm90cm9waWMKYGBge3J9CmFmcm90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhID0gY29tbXVuaXR5X2RhdGFfbWV0cmljcyAlPiUgZmlsdGVyKGNvcmVfcmVhbG0gPT0gJ0Fmcm90cm9waWMnKQphZnJvdHJvcGljX2NpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgZHBseXI6OnNlbGVjdChjaXR5X25hbWUpICU+JSBkaXN0aW5jdCgpICU+JSBhcy5saXN0KCkKYGBgCgpgYGB7cn0KYWZyb3Ryb3BpY19jaXRpZXNfbm1kcyA9IGNvbW11bml0eV9ubWRzKGNvbW11bml0aWVzICU+JSBmaWx0ZXIoY2l0eV9pZCAlaW4lIGFmcm90cm9waWNfY2l0aWVzX2NvbW11bml0eV9kYXRhJGNpdHlfaWQpKSAKYWZyb3Ryb3BpY19jaXRpZXNfbm1kcwpgYGAKCmBgYHtyfQpzY3JlZV9wbG90KGFmcm90cm9waWNfY2l0aWVzX25tZHMpCmBgYAoKYGBge3J9CmFmcm90cm9waWNfY2l0aWVzID0gY2x1c3Rlcl9jaXRpZXMoY2l0eV9ubWRzID0gYWZyb3Ryb3BpY19jaXRpZXNfbm1kcywgY2l0aWVzX2NvbW11bml0eV9kYXRhID0gYWZyb3Ryb3BpY19jaXRpZXNfY29tbXVuaXR5X2RhdGEsIGNlbnRlcnMgPSAyKQpgYGAKCmBgYHtyfQpwbG90X25tZHNfY2x1c3RlcnMoYWZyb3Ryb3BpY19jaXRpZXMpCmBgYAoKYGBge3J9CmFmcm90cm9waWNfYmlvbWVzID0gcmVzb2x2ZVtyZXNvbHZlJFJFQUxNID09ICdBZnJvdHJvcGljJyxjKCdSRUFMTScpXQogCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gYWZyb3Ryb3BpY19iaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBhZnJvdHJvcGljX2NpdGllcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnksIGNvbG9yID0gY2x1c3RlcikpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnYWZyb3Ryb3BpY19jbHVzdGVycy5qcGcnKSkKYGBgCgojIyMgQWZyb3Ryb3BpYyBDbHVzdGVyIDEKYGBge3J9CmFmcm90cm9waWNfY2x1c3RlcjEgPSBhZnJvdHJvcGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkKcGxvdF9jaXR5X2NsdXN0ZXIoYWZyb3Ryb3BpY19jbHVzdGVyMSwgJ0Fmcm90cm9waWMgY2x1c3RlciAxJykKZ2dzYXZlKGZpbGVuYW1lKFJFR0lPTl9ERUVQX0RJVkVfRklHVVJFU19PVVRQVVQsICdhZnJvdHJvcGljX2NsdXN0ZXIxLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChhZnJvdHJvcGljX2NsdXN0ZXIxKSwgaGVpZ2h0ID0gZ2V0X2ltYWdlX2hlaWdodChhZnJvdHJvcGljX2NsdXN0ZXIxKSwgdW5pdHMgPSAibW0iKQpgYGAKCiMjIyBBZnJvdHJvcGljIENsdXN0ZXIgMgpgYGB7cn0KYWZyb3Ryb3BpY19jbHVzdGVyMiA9IGFmcm90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKQpwbG90X2NpdHlfY2x1c3RlcihhZnJvdHJvcGljX2NsdXN0ZXIyLCAnQWZyb3Ryb3BpYyBjbHVzdGVyIDInKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2Fmcm90cm9waWNfY2x1c3RlcjIuanBnJyksIHdpZHRoID0gZ2V0X2ltYWdlX3dpZHRoKGFmcm90cm9waWNfY2x1c3RlcjIpLCBoZWlnaHQgPSBnZXRfaW1hZ2VfaGVpZ2h0KGFmcm90cm9waWNfY2x1c3RlcjIpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMgSW5kb21hbGF5YW4KYGBge3J9CmluZG9tYWxheWFuX2NpdGllc19jb21tdW5pdHlfZGF0YSA9IGNvbW11bml0eV9kYXRhX21ldHJpY3MgJT4lIGZpbHRlcihjb3JlX3JlYWxtID09ICdJbmRvbWFsYXlhbicpCmluZG9tYWxheWFuX2NpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgZHBseXI6OnNlbGVjdChjaXR5X25hbWUpICU+JSBkaXN0aW5jdCgpICU+JSBhcy5saXN0KCkKYGBgCgpgYGB7cn0KaW5kb21hbGF5YW5fY2l0aWVzX25tZHMgPSBjb21tdW5pdHlfbm1kcyhjb21tdW5pdGllcyAlPiUgZmlsdGVyKGNpdHlfaWQgJWluJSBpbmRvbWFsYXlhbl9jaXRpZXNfY29tbXVuaXR5X2RhdGEkY2l0eV9pZCkpIAppbmRvbWFsYXlhbl9jaXRpZXNfbm1kcwpgYGAKCmBgYHtyfQpzY3JlZV9wbG90KGluZG9tYWxheWFuX2NpdGllc19ubWRzKQpgYGAKCmBgYHtyfQppbmRvbWFsYXlhbl9jaXRpZXMgPSBjbHVzdGVyX2NpdGllcyhjaXR5X25tZHMgPSBpbmRvbWFsYXlhbl9jaXRpZXNfbm1kcywgY2l0aWVzX2NvbW11bml0eV9kYXRhID0gaW5kb21hbGF5YW5fY2l0aWVzX2NvbW11bml0eV9kYXRhLCBjZW50ZXJzID0gNSkKYGBgCgpgYGB7cn0KcGxvdF9ubWRzX2NsdXN0ZXJzKGluZG9tYWxheWFuX2NpdGllcykKYGBgCgpgYGB7cn0KaW5kb21hbGF5YW5fYmlvbWVzID0gcmVzb2x2ZVtyZXNvbHZlJFJFQUxNID09ICdJbmRvbWFsYXlhbicsYygnUkVBTE0nKV0KIApnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IGluZG9tYWxheWFuX2Jpb21lcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnkpKSArIAogIGdlb21fc2YoZGF0YSA9IGluZG9tYWxheWFuX2NpdGllcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnksIGNvbG9yID0gY2x1c3RlcikpCmdnc2F2ZShmaWxlbmFtZShSRUdJT05fREVFUF9ESVZFX0ZJR1VSRVNfT1VUUFVULCAnaW5kb21hbGF5YW5fY2x1c3RlcnMuanBnJykpCmBgYAoKIyMjIEluZG9tYWxheWFuIENsdXN0ZXIgMQpgYGB7cn0KaW5kb21hbGF5YW5fY2x1c3RlcjEgPSBpbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDEpCnBsb3RfY2l0eV9jbHVzdGVyKGFmcm90cm9waWNfY2x1c3RlcjIsICdJbmRvbWFsYXlhbiBjbHVzdGVyIDEnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2luZG9tYWxheWFuX2NsdXN0ZXIxLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChpbmRvbWFsYXlhbl9jbHVzdGVyMSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoaW5kb21hbGF5YW5fY2x1c3RlcjEpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIEluZG9tYWxheWFuIENsdXN0ZXIgMgpgYGB7cn0KaW5kb21hbGF5YW5fY2x1c3RlcjIgPSBpbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDIpCnBsb3RfY2l0eV9jbHVzdGVyKGFmcm90cm9waWNfY2x1c3RlcjIsICdJbmRvbWFsYXlhbiBjbHVzdGVyIDInKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2luZG9tYWxheWFuX2NsdXN0ZXIyLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChpbmRvbWFsYXlhbl9jbHVzdGVyMiksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoaW5kb21hbGF5YW5fY2x1c3RlcjIpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIEluZG9tYWxheWFuIENsdXN0ZXIgMwpgYGB7cn0KaW5kb21hbGF5YW5fY2x1c3RlcjMgPSBpbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDMpCnBsb3RfY2l0eV9jbHVzdGVyKGFmcm90cm9waWNfY2x1c3RlcjIsICdJbmRvbWFsYXlhbiBjbHVzdGVyIDMnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2luZG9tYWxheWFuX2NsdXN0ZXIzLmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChpbmRvbWFsYXlhbl9jbHVzdGVyMyksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoaW5kb21hbGF5YW5fY2x1c3RlcjMpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIEluZG9tYWxheWFuIENsdXN0ZXIgNApgYGB7cn0KaW5kb21hbGF5YW5fY2x1c3RlcjQgPSBpbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDQpCnBsb3RfY2l0eV9jbHVzdGVyKGFmcm90cm9waWNfY2x1c3RlcjIsICdJbmRvbWFsYXlhbiBjbHVzdGVyIDQnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2luZG9tYWxheWFuX2NsdXN0ZXI0LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChpbmRvbWFsYXlhbl9jbHVzdGVyNCksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoaW5kb21hbGF5YW5fY2x1c3RlcjQpLCB1bml0cyA9ICJtbSIpCmBgYAoKIyMjIEluZG9tYWxheWFuIENsdXN0ZXIgNQpgYGB7cn0KaW5kb21hbGF5YW5fY2x1c3RlcjUgPSBpbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDUpCnBsb3RfY2l0eV9jbHVzdGVyKGFmcm90cm9waWNfY2x1c3RlcjIsICdJbmRvbWFsYXlhbiBjbHVzdGVyIDUnKQpnZ3NhdmUoZmlsZW5hbWUoUkVHSU9OX0RFRVBfRElWRV9GSUdVUkVTX09VVFBVVCwgJ2luZG9tYWxheWFuX2NsdXN0ZXI1LmpwZycpLCB3aWR0aCA9IGdldF9pbWFnZV93aWR0aChpbmRvbWFsYXlhbl9jbHVzdGVyNSksIGhlaWdodCA9IGdldF9pbWFnZV9oZWlnaHQoaW5kb21hbGF5YW5fY2x1c3RlcjUpLCB1bml0cyA9ICJtbSIpCmBgYAoK